#include "game_player.h"
#include "SDL2/SDL.h"
#include "sdl_helper.h"
#include "game_map.h"
#include "game_math.h"

#define PLAYER_MULTIPLIER(p) (float) (p->level *0.2)
#define PLAYER_JUMP(p) (-1 -p->level *0.2)
#define PLAYER_SPEED_RUNNING(p) (0.1 +p->level *0.05)
#define PLAYER_SPEED_MAX(p) (1 +p->level *0.1)
#define PLAYER_BUMP_POWER(p) (2 +p->level *0.2)
#define PLAYER_ATTACK_POWER(p) (5 +p->level *0.8)
#define PLAYER_CAST_COOLDOWN(p) (30 -p->level *1)

/* animation states - used to change frames on the player's texture
 * the texture is divined on the x axis by animation frames,
 * and on the y axis by animations
 */
const int ANIMATION_IDLE = 0;
const int ANIMATION_RUN  = 1;
const int ANIMATION_JUMP = 2;
const int ANIMATION_FALL = 3;
const int ANIMATION_CAST = 4;
const int ANIMATION_ATTACK = 4;

/* secret functions - not meant for the public's eyes
 */

// cast is the action of the skill itself
void game_player_cast(struct game_player *p) {

	/* currently the only skill is to increase the height the platform the player stands on
	 * and the two surrrounding platforms
	 */
	int platform_id = game_map_platform_id(p->map, p->rect.x);
	game_map_platform_rise(p->map, platform_id, -15);
	game_map_platform_rise(p->map, platform_id-1, -10);
	game_map_platform_rise(p->map, platform_id+1, -10);

	// wait ~1 second before casting again
	p->casting_cooldown = PLAYER_CAST_COOLDOWN(p);

}

// animate
int game_player_animate(struct game_player *p) {

	/* advance animation frames
	 * currently it does so every 10 frames, but this might change in the future
	 */
	p->anim_count++;
	if (p->anim_count > 10) {

		// animate player's texture to next frame
		p->tex_rect.x += p->tex_size;

		// reset animation counter
		p->anim_count = 0;

		// end of animation - repeat
		if (p->tex_rect.x > p->tex_size *3) {

			// reset animation frame
			p->tex_rect.x = 0;

			// animation ended
			return 1;

		}

	} // player frame animation

	// animation keeps going
	return 0;

} // animate

// collision player with water
int game_player_water_collide(struct game_player *p) {
	return p->rect.y +p->rect.h > game_map_water_y(p->map);
}

// collision player with water - deep enough to drown
int game_player_water_drown(struct game_player *p) {
	return p->rect.y > game_map_water_y(p->map);
}

void game_player_movement(struct game_player *p) {

	/* handle movement
	 * if user is moving left or right, move the velocity_x towards -1 or 1
	 * the final speed of the character is its own speed times speec_counter
	 * if user is not moving, slowly decelerate
	 */
	switch (p->move) {
		// not moving - rest
		case MOVE_NONE:
			p->velocity_x *= 0.8;
			if (p->velocity_x < 0.1
			&&  p->velocity_x > -0.1) p->velocity_x = 0;
			break;

		// moving rightwards
		case MOVE_RIGHT:
			p->velocity_x += PLAYER_SPEED_RUNNING(p);
			if (p->velocity_x > PLAYER_SPEED_MAX(p)) p->velocity_x = PLAYER_SPEED_MAX(p);
			break;

		// moving leftwards
		case MOVE_LEFT:
			p->velocity_x -= PLAYER_SPEED_RUNNING(p);
			if (p->velocity_x < -PLAYER_SPEED_MAX(p)) p->velocity_x = -PLAYER_SPEED_MAX(p);
			break;
	}

	// temporary way of assigning the running animation based on speed
	if (p->velocity_x > 0 && p->state == IDLE) {
		p->tex_rect.y = p->tex_size *ANIMATION_RUN;
	}
	else if (p->velocity_x < 0 && p->state == IDLE) {
		p->tex_rect.y = p->tex_size *ANIMATION_RUN;
	}
	else if (p->state == IDLE) {
		p->tex_rect.y = p->tex_size *ANIMATION_IDLE;
	}

	/* movement & collision
	 */
	if (p->velocity_x != 0) {

		// player is in the water, delay speed
		if (game_player_water_collide(p)) {
			p->velocity_x *= 0.8;
		}

		// movement
		p->rect.x += p->velocity_x *p->speed_max;

		// check collision against each player
		for (int i = 0; i < 2; i++) {

			// not on self
			if (&p->players[i] == p) continue;

			// actual collision
			if (game_math_collide(&p->players[i].rect, &p->rect)) {
				p->rect.x = p->players[i].rect.x +(p->velocity_x > 0 ? -p->rect.w : +p->rect.w);
				break;
			}
		}

	} // movement

	// moved to a new platform
	if (game_map_platform_id(p->map, p->rect.x) != p->platform_id) {

		// get new platform id and height
		int new_platform_id = game_map_platform_id(p->map, p->rect.x);
		int new_platform_y = game_map_platform_y(p->map, new_platform_id);

		// platform is above player, don't let them move there!
		if (new_platform_y < p->rect.y +p->rect.h) {

			// move player back
			if (new_platform_id > p->platform_id) {
				p->rect.x = (p->platform_id+1) *p->map->grid_width -1 +p->map->offset_x;
			}
			else {
				p->rect.x = p->platform_id *p->map->grid_width +p->map->offset_x;
			}

			// cancel player's speed
			p->velocity_x = 0;

		}
		// platform is below (or same level as) player, assign new platform to player
		else {
			p->platform_id = new_platform_id;
			p->state = FALL;
		}
	}

}

/* initialise player
 * for now initialise him on the top left part of the screen
 * with 48x48 size
 * not moving, not standing on a surface
 * initialise some sample speed and falling/jumping data
 */
void game_player_init(struct game_player *p, struct game_map *m) {

	// player's size (width and height)
	p->tex_size = 48;

	// player's texture and rect
	p->tex = load_image("images/character_0.png");
	p->rect.x = 0;
	p->rect.y = 0;
	p->rect.w = p->tex_size;
	p->rect.h = p->tex_size;

	/* player's texture rect, should always have the same width and height,
	 * the x and y should change to point to the specific animation/frame
	 */
	p->tex_rect.x = 0;
	p->tex_rect.y = 0;
	p->tex_rect.w = p->tex_size;
	p->tex_rect.h = p->tex_size;

	// animation
	p->state = IDLE;
	p->anim_count = 0;
	p->looking_side = LOOKING_RIGHT;

	// movement
	p->move = MOVE_NONE;
	p->speed_max = 5;
	p->velocity_x = 0;
	p->velocity_y = 0;

	/* get the map and mark where the player is on the map
	 * based on starting position
	 */
	p->map = m;
	p->platform_id = game_map_platform_id(p->map, p->rect.x);

	// ai
	p->map_target = 0;

	// casting skill variables
	p->casting_cooldown = 0;

	// player's starting level
	p->level = 0;

} // init

/* the player when updates:
 * moves left/right
 * moves up/down with gravity
 */
void game_player_update(struct game_player *p) {

	// casting is on cooldown, decrease it
	if (p->casting_cooldown > 0) p->casting_cooldown--;

	/* ideally the whole update function should use this switch
	 * do something depending on its state
	 */
	int platform_y = game_map_platform_y(p->map, p->platform_id);
	switch (p->state) {

		/* when attacking, move to the looking direction non-stop,
		 * until animation runs out, or player collides with another player
		 * or the map
		 */
		case ATTACK:

			// when animation is over, fall to the platform
			if (game_player_animate(p)) {
				p->state = FALL;

				// a small velocity makes a nice after-attack bouncing effect
				p->velocity_y = -0.5;
				p->velocity_x = -1 *p->looking_side;
			}

			// actual movement towards looking direction
			p->rect.x += PLAYER_ATTACK_POWER(p) *p->looking_side;

			// check collision with other players (for now with magic numbers)
			for (int i = 1; i < 2; i++) {

				// check collision on x and y
				if (game_math_collide(&p->players[i].rect, &p->rect)) {

					p->rect.x = p->players[i].rect.x +(p->looking_side == LOOKING_RIGHT ? -p->rect.w -1 : p->rect.w +5);

					// if collision happens, stop attacking, move current player with a "bump" jump away
					p->state = FALL;
					p->velocity_y = -0.5;
					p->velocity_x = -1 *p->looking_side;

					// ideally, the targeted player is bounced away here
					game_player_bump(&p->players[i], p);

					// only first player matters
					break;
				}

			} // collision with players

			// moved to new platform
			int new_id;
			if ((new_id = game_map_platform_id(p->map, p->rect.x)) != p->platform_id) {

				// not colliding with it, use it as current platform
				if (p->rect.y +p->rect.h < game_map_platform_y(p->map, new_id)) {
					p->platform_id = new_id;
					p->state = FALL;
				}
				// colliding with it, stop, and attack it
				else {
					p->rect.x = game_map_platform_x(p->map, p->platform_id)
						+(new_id > p->platform_id? 
							game_map_platform_width(p->map) : 0);
					p->state = FALL;
					p->velocity_y = -0.5;
					p->velocity_x = -1 *p->looking_side;

					// get attacking platform (either left or right)
					int target_platform = p->platform_id
						+(p->looking_side == LOOKING_RIGHT ? 1: -1);

					// attack platform
					game_map_platform_rise(p->map, target_platform, 10);
				}
			}

			// for now ignore the rest of update
			return;

		// player is sitting on the ground
		case IDLE:
			game_player_animate(p);

			// follow standing platform
			p->rect.y = platform_y -p->rect.h;

			// nullify speed for now
			p->velocity_y = 0;

			// movement
			game_player_movement(p);
			break;

		// player is jumping
		case JUMP:
			game_player_animate(p);

			/* gravity
			 * as long as the player stands on a surface, nothing happens
			 * if player is jumping, advance the jump counter, which goes
			 * from 0 to -1, once its done, player stops jumping, and is now falling
			 * because of gravity
			 */
			p->velocity_y -= 0.4;
			if (p->velocity_y <= PLAYER_JUMP(p)) {
				p->state = FALL;
			}

			// animation is jumping
			p->tex_rect.y = p->tex_size *ANIMATION_JUMP;

			// move player up/down, if player goes below platform, they are now standing on it
			p->rect.y += p->velocity_y * 10;
			if (game_player_water_drown(p)) {
				p->state = DROWN;
			}
			else
			if (p->rect.y +p->rect.h > platform_y) {
				p->rect.y = platform_y -p->rect.h;
				p->velocity_y = 0;
				p->state = IDLE;
			}

			// collision on players
			for (int i = 0; i < 2; i++) {

				// not on self
				if (&p->players[i] == p) continue;

				// actual collision
				if (game_math_collide(&p->players[i].rect, &p->rect)) {
					p->rect.y = p->players[i].rect.y +(p->velocity_y > 0 ? -p->rect.h : p->rect.h);
					p->state = FALL;
					p->velocity_y = 0;
					break;
				}
			}

			// when jumping, the character still moves left/right
			game_player_movement(p);
			break;

		// player is falling
		case FALL:
			game_player_animate(p);

			// player keeps falling at increasing capped speed
			p->velocity_y += 0.2;
			if (p->velocity_y > 1) p->velocity_y = 1;

			if (p->velocity_y > 0) {
				// animation is falling
				p->tex_rect.y = p->tex_size *ANIMATION_FALL;
			}
			else {
				// animation is jumping
				p->tex_rect.y = p->tex_size *ANIMATION_JUMP;
			}

			// move player up/down, if player goes below platform, they are now standing on it
			p->rect.y += p->velocity_y * 10;
			if (game_player_water_drown(p)) {
				p->state = DROWN;
			}
			else
			if (p->rect.y +p->rect.h > platform_y) {
				p->rect.y = platform_y -p->rect.h;
				p->velocity_y = 0;
				p->state = IDLE;
			}
			// if player touches another player, step on them
			else {
				for (int i = 0; i < 2; i++) {

					// not on self
					if (&p->players[i] == p) continue;

					// actual collision
					if (game_math_collide(&p->players[i].rect, &p->rect)) {
						p->rect.y = p->players[i].rect.y +(p->velocity_y > 0 ? -p->rect.h : p->rect.h);
						p->velocity_y = 0;
						break;
					}
				}
			}

			// when falling, the character still moves left/right
			game_player_movement(p);
			break;

		// player is casting skill
		case CAST:
			// casting is done, apply effect and fall
			if (game_player_animate(p)) {
				game_player_cast(p);
				p->state = FALL;
			}

			// don't move during casting
			p->move = MOVE_NONE;

			// follow standing platform
			p->rect.y = platform_y -p->rect.h;

			// nullify speed for now
			p->velocity_y = 0;
			break;

		/* players drown when falling deep enough in the water
		 * they remain there, floating, forever
		 */
		case DROWN:
			// for now do nothing
			return;

		default:
			break;
	}

} // update

/* draw player using its rect
 */
void game_player_draw(struct game_player *p) {
	p->rect.x -= p->tex_size/2;
	if (p->looking_side == LOOKING_RIGHT) {
		p->tex_rect.x += 192; // 4 frames * 48 pixels = 192
	}
	SDL_RenderCopy(ren, p->tex, &p->tex_rect, &p->rect);
	if (p->looking_side == LOOKING_RIGHT) {
		p->tex_rect.x -= 192;
	}
	p->rect.x += p->tex_size/2;
}

/* movement
 */
void game_player_moveleft(struct game_player *p) {

	// some states take control away
	if (p->state == CAST || p->state == DROWN || p->state == ATTACK) return;

	p->move = MOVE_LEFT;
	p->looking_side = LOOKING_LEFT;
}

void game_player_moveright(struct game_player *p) {

	// some states take control away
	if (p->state == CAST || p->state == DROWN || p->state == ATTACK) return;

	p->move = MOVE_RIGHT;
	p->looking_side = LOOKING_RIGHT;
}

void game_player_movestop(struct game_player *p) {
	p->move = MOVE_NONE;
}

/* jump
 */
void game_player_jump(struct game_player *p) {
	if (p->state == IDLE) {
		p->state = JUMP;
	}
}

// player's special skill
void game_player_useskill(struct game_player *p) {

	// skills usage:
	// only on platform
	// can't use while in water
	// can't use when in cooldown
	if (p->state != IDLE
	|| game_player_water_collide(p)
	|| p->casting_cooldown > 0) return;

	p->state = CAST;
	p->anim_count = 0;
	p->tex_rect.x = 0;
	p->tex_rect.y = p->tex_size *ANIMATION_CAST;

} // use skill

/* player's attack
 * change state (for behaviour) and texture rect (to animate properly)
 */
void game_player_attack(struct game_player *p) {

	// attack is only allowed while player is on the platform (not doing something else)
	if (p->state != IDLE) {
		return;
	}

	p->state = ATTACK;
	p->anim_count = 0;
	p->tex_rect.x = 0;
	p->tex_rect.y = p->tex_size *ANIMATION_ATTACK;

} // attack

// ai behaviour
void game_player_ai(struct game_player *p) {

	/* find the tallest map platform
	 * is iterated platform taller?
	 * then player's target is that platform now
	 */
	for (int i = 0; i < 10; i++) {
		if (game_map_platform_y(p->map, i) < game_map_platform_y(p->map, p->map_target)) {
			p->map_target = i;
		}
	}

	// find target platform's x
	int target_x = game_map_platform_x(p->map, p->map_target) +p->map->grid_width/2;

	// approach it
	if (p->rect.x > target_x) {
		game_player_moveleft(p);
	}
	else {
		game_player_moveright(p);
	}

	// if too close to target, don't do anything else
	if (p->rect.x -target_x > -p->map->grid_width/3
	&&  p->rect.x -target_x <  p->map->grid_width/3) {
		game_player_movestop(p);
		game_player_useskill(p);
		return;
	}

	/* if standing on platform
	 * and target platform is taller
	 * jump
	 */
	if (p->state == IDLE
	&& game_map_platform_y(p->map, p->map_target) < game_map_platform_y(p->map, p->platform_id)) {
		game_player_jump(p);
	}

} // ai

void game_player_bump(struct game_player *p, struct game_player *p2) {

	int from_direction = p2->looking_side;

	p->velocity_x = PLAYER_BUMP_POWER(p2) *from_direction;
	p->velocity_y = -1;
	p->state = FALL;
	p->looking_side = from_direction == LOOKING_LEFT ? LOOKING_RIGHT : LOOKING_LEFT;

}

// collision data
int game_player_left(struct game_player *p) { return p->rect.x; }
int game_player_top (struct game_player *p) { return p->rect.y; }
int game_player_right (struct game_player *p) { return p->rect.x +p->rect.w; }
int game_player_bottom(struct game_player *p) { return p->rect.y +p->rect.h; }
int game_player_centerx(struct game_player *p) { return p->rect.x +p->rect.w/2; }
int game_player_centery(struct game_player *p) { return p->rect.y +p->rect.h/2; }

void game_player_offset(struct game_player *s, int x, int y) {
	s->rect.x += x;
	s->rect.y += y;
}

int game_player_get_state(struct game_player *s) { return s->state; }
